<template>
  <teleport to="body">
    <transition @enter="transitionEnter" @after-enter="transitionAfterEnter" @leave="transitionLeave">
      <dl v-if="visible" :style="contextMenuStyle" class="system-context-menu" ref="contextMenuDom">
        <template v-for="(item, itemIndex) in menu" :key="itemIndex">
          <dd
            v-for="child in item"
            :key="child.id"
            :class="{ disabled: child.disabled }"
            @click.stop="handleClickMenu(child)"
            @mouseenter.stop="menuMouseenter($event, child)"
            @mouseleave.stop="menuMouseLeave"
          >
            <span class="icon" v-if="child.isChecked">
              <i class="iconfont fs12" :class="child.checkIcon" />
            </span>
            {{ child.name }}
            <span class="child-icon" v-if="child.children?.length">
              <i class="iconfont wos-to-right" />
            </span>
          </dd>
          <dt v-if="itemIndex !== menu.length - 1"></dt>
        </template>
      </dl>
    </transition>
  </teleport>
  <teleport to="body">
    <template v-if="childrenMenu.length">
      <dl
        class="system-context-menu-child-menu"
        ref="contextChildrenMenuDom"
        :style="contextChildrenMenuStyle"
        @mouseenter="childrenMenuMouseenter"
        @mouseleave="menuMouseLeave"
      >
        <template v-for="(item, itemIndex) in childrenMenu" :key="itemIndex">
          <dd
            v-for="child in item"
            :key="child.id"
            :class="{ disabled: child.disabled }"
            @click.stop="handleClickMenu(child)"
          >
            <span class="icon" v-if="child.isChecked">
              <i class="iconfont fs12" :class="child.checkIcon" />
            </span>
            {{ child.name }}
          </dd>
          <dt v-if="itemIndex !== childrenMenu.length - 1"></dt>
        </template>
      </dl>
    </template>
  </teleport>
</template>

<script lang="ts">
import { computed, defineComponent, reactive, ref, watch } from 'vue'
import { useClickBlankHidden } from '@/hooks'
import { useContextMenuStore } from '@/stores/desk/contextMenu'
import { getZIndex } from '@/utils/utils'
import type { PropType } from 'vue'
import type { IContextMenu, IMenu } from '@/stores/desk/contextMenu'

export default defineComponent({
  name: 'systemContextMenu',
  props: {
    modelValue: {
      type: Boolean as PropType<boolean>,
      required: true,
      default: false
    },
    menu: {
      type: Array as PropType<IContextMenu[keyof IContextMenu]>,
      required: true
    },
    position: {
      type: Array as PropType<number[]>,
      required: true,
      default: () => [0, 0]
    }
  },
  emits: ['update:modelValue', 'menuClick'],
  setup(props, ctx) {
    const contextMenuStore = useContextMenuStore()

    useClickBlankHidden([() => contextMenuDom.value, () => contextChildrenMenuDom.value], () => {
      visible.value = false
      childrenMenu.value = []
      ctx.emit('update:modelValue', false)
    })

    // hover菜单timer
    let menuMouseLeaveTimer: ReturnType<typeof setTimeout>
    // 菜单显示
    const visible = ref<boolean>(props.modelValue)
    // 菜单DOM
    const contextMenuDom = ref<HTMLElement>()
    // 子菜单DOM
    const contextChildrenMenuDom = ref<HTMLElement>()
    // 子菜单数据
    const childrenMenu = ref<IMenu[][]>([])
    // 子菜单位置
    const childrenMenuPosition = reactive<{ left: number; top: number; width: number }>({ left: 0, top: 0, width: 0 })
    // 菜单样式
    const contextMenuStyle = computed(() => {
      if (!contextMenuDom.value) return undefined
      const domWidth = contextMenuDom.value.offsetWidth
      const domHeight = contextMenuDom.value.scrollHeight
      let left = props.position[0] + domWidth
      let top = props.position[1] + domHeight
      left = left > innerWidth ? innerWidth - domWidth : props.position[0]
      top = top > innerHeight ? props.position[1] - domHeight : props.position[1]

      return {
        left: `${left}px`,
        top: `${top}px`
      }
    })
    // 子菜单样式
    const contextChildrenMenuStyle = computed(() => {
      if (!contextChildrenMenuDom.value) return undefined
      const childWidth = contextChildrenMenuDom.value?.offsetWidth
      const childHeight = contextChildrenMenuDom.value?.scrollHeight
      let left = childrenMenuPosition.left + childrenMenuPosition.width - 10
      let top = childrenMenuPosition.top - 5
      left = left + childWidth > innerWidth ? childrenMenuPosition.left - childWidth : left
      top = top + childHeight > innerHeight ? childrenMenuPosition.top - childHeight : top
      return {
        left: `${left}px`,
        top: `${top}px`,
        zIndex: getZIndex()
      }
    })

    watch(
      () => props.modelValue,
      (val: boolean) => {
        visible.value = val
      }
    )

    const transitionEnter = (el: Element) => {
      const dom = el as HTMLElement
      dom.style.overflow = 'hidden'
      dom.style.zIndex = `${getZIndex()}`
      dom.style.height = `${dom.scrollHeight}px`
    }

    const transitionAfterEnter = (el: Element) => {
      const dom = el as HTMLElement
      dom.style.overflow = ''
    }

    const transitionLeave = (el: Element) => {
      const dom = el as HTMLElement
      dom.style.opacity = '0'
    }

    const handleClickMenu = (menu: IMenu) => {
      if (menu.disabled || menu.children?.length) return
      childrenMenu.value = []
      visible.value = false
      contextMenuStore.checkMenu(menu.id, menu.radio)
      ctx.emit('update:modelValue', false)
      ctx.emit('menuClick', menu)
    }

    const menuMouseenter = (ev: MouseEvent, menu: IMenu) => {
      if (!menu.children?.length || !visible.value || menu.disabled) return
      menuMouseLeaveTimer && clearTimeout(menuMouseLeaveTimer)
      const target = ev.target as HTMLElement
      const { left, top, width } = target.getBoundingClientRect()
      childrenMenuPosition.left = left
      childrenMenuPosition.top = top
      childrenMenuPosition.width = width
      childrenMenu.value = menu.children
    }

    const menuMouseLeave = () => {
      menuMouseLeaveTimer && clearTimeout(menuMouseLeaveTimer)
      menuMouseLeaveTimer = setTimeout(() => {
        childrenMenuPosition.left = 0
        childrenMenuPosition.top = 0
        childrenMenuPosition.width = 0
        childrenMenu.value = []
      }, 100)
    }

    const childrenMenuMouseenter = () => {
      menuMouseLeaveTimer && clearTimeout(menuMouseLeaveTimer)
    }

    return {
      visible,
      childrenMenu,
      contextMenuDom,
      contextMenuStyle,
      contextChildrenMenuDom,
      contextChildrenMenuStyle,
      menuMouseenter,
      menuMouseLeave,
      childrenMenuMouseenter,
      handleClickMenu,
      transitionEnter,
      transitionAfterEnter,
      transitionLeave
    }
  }
})
</script>

<style scoped lang="scss">
.system-context-menu,
.system-context-menu-child-menu {
  width: 250px;
  height: 0;
  position: fixed;
  color: var(--base-font);
  background-color: var(--system-context-menu-bg-color);
  border: 1px solid var(--system-context-menu-border-color);
  transition:
    height 0.25s,
    opacity 0.2s;

  dt {
    margin: 4px auto;
    width: calc(100% - 20px);
    height: 1px;
    background-color: var(--system-context-menu-divider-color);
  }

  dd {
    height: 24px;
    line-height: 24px;
    padding: 0 10px 0 35px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    position: relative;

    &:first-of-type {
      margin-top: 5px;
    }

    &:last-of-type {
      margin-bottom: 5px;
    }

    &.disabled {
      cursor: not-allowed;
      color: rgb(109, 109, 109);
    }

    &:hover {
      background-color: var(--system-context-menu-hover-color);

      .system-context-menu-child-menu {
        display: block;
      }
    }

    > .child-icon {
      display: inline-block;
      transform: scaleX(0.8);
    }

    > .icon {
      position: absolute;
      left: 10px;
      color: var(--base-font);

      .wos-dot {
        color: var(--base-font);
      }
    }
  }
}

.system-context-menu-child-menu {
  width: 220px;
  height: auto;
}
</style>
